package com.rajaev.dtu.tractor.domain.service;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.log.StaticLog;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.mongodb.client.MongoCursor;
import com.rajaev.dtu.tractor.app.service.BatteryService;
import com.rajaev.dtu.tractor.domain.entity.BatteryOnlineLogPo;
import com.rajaev.dtu.tractor.domain.entity.BatteryPo;
import com.rajaev.dtu.tractor.domain.entity.Protocol485Dto;
import com.rajaev.dtu.tractor.domain.repository.mysql.BatteryDao;
import com.rajaev.dtu.tractor.domain.repository.mysql.BatteryOnlineLogDao;
import com.rajaev.dtu.tractor.domain.repository.mysql.CustomerDao;
import com.rajaev.dtu.tractor.domain.repository.mysql.UserDao;
import com.rajaev.dtu.tractor.domain.repository.redis.DeviceGPSRedisRepository;
import com.rajaev.dtu.tractor.domain.repository.redis.DeviceMonitorRedisRepository;
import com.rajaev.dtu.tractor.domain.vo.*;
import com.rajaev.dtu.tractor.infrastructure.base.common.Constants;
import com.rajaev.dtu.tractor.infrastructure.base.enums.BatteryOnlineStatus;
import com.rajaev.dtu.tractor.infrastructure.base.enums.BatteryStateEnum;
import com.rajaev.dtu.tractor.infrastructure.base.enums.BatteryWorkStateEnum;
import com.rajaev.dtu.tractor.infrastructure.base.errors.BusinessException;
import com.rajaev.dtu.tractor.infrastructure.base.errors.ErrorCodes;
import com.rajaev.dtu.tractor.infrastructure.helper.MongoDbUtils;
import com.rajaev.dtu.tractor.infrastructure.helper.TimeUtil;
import com.rajaev.dtu.tractor.interfaces.assembler.AssemblerFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.IteratorUtils;
import org.bson.codecs.configuration.CodecRegistry;
import org.dozer.Mapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.geo.Point;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import static com.mongodb.client.model.Filters.*;

/**
 * @author tianjie
 */
@Service
@Slf4j
public class BatteryServiceImpl implements BatteryService {


    @Autowired
    BatteryDao batteryDao;

    @Autowired
    UserDao userDao;

    @Autowired
    Mapper dozermapper;

    @Autowired
    DeviceMonitorRedisRepository deviceMonitorRedisRepository;

    @Autowired
    DeviceGPSRedisRepository deviceGPSRedisRepository;

    @Autowired
    CustomerDao customerDao;

    @Autowired
    ApplicationContext applicationContext;

    @Autowired
    BatteryOnlineLogDao batteryOnlineLogDao;


    @Autowired
    MongoDbUtils mongoDbUtils;

    @Autowired
    CodecRegistry codecRegistry;


    @Override
    public List<String> selectVins(String keyword) {
        return batteryDao.selectVins(keyword);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void save(String vin) throws BusinessException {
        if (batteryDao.countByVin(vin) > 0) {
            throw new BusinessException(ErrorCodes.ERR_VIN_IS_EXISTS);
        }

        BatteryPo batteryPo = new BatteryPo();
        batteryPo.setVin(vin);
        List<CustomerVo> list = customerDao.selectCustomerByName("雅骏");
        if (list != null && list.size() > 0) {
            batteryPo.setCustomerId(list.get(0).getId());
        } else {
            throw new BusinessException("没有设置默认的客户");
        }

        if (1 != batteryDao.saveBatteryVin(batteryPo)) {
            throw new BusinessException(ErrorCodes.ERR_UPDATE_0_ROWS);
        }

    }

    @Override
    public BatteryWorkStateEnum getBatteryWorkState(String vin) {
        Integer xtzt = deviceMonitorRedisRepository.getDeviceMonitor(vin).getDcxtzt();// xtzt : 系统状态
        Optional<BatteryWorkStateEnum> optional = BatteryWorkStateEnum.stateOf(String.valueOf(xtzt));
        if (optional.isPresent()) {
            return optional.get();
        }
        return null;
    }

    @Override
    public void delete(String vin) {
        batteryDao.deleteByVin(vin);
    }


    @Override
    public BatteryMonitorVo selectByVin(String vin) {
        BatteryPo batteryPo = batteryDao.selectByVin(vin);
        return parseBatteryMonitor(batteryPo);
    }

    @Override
    public List<BatteryMonitorVo> selectByLastVin4(String lastVin4, Integer pageSize, Integer pageNo) {
        List<BatteryPo> batteryPos = batteryDao.selectByLastVin4(lastVin4);
        List<BatteryMonitorVo> list = selectBatteryMonitors(batteryPos);

        return list;
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batteryLogin(BatteryPo batteryPo,String mac,Date loginTime) {
        BatteryOnlineLogPo batteryOnlineLogPo = createBatteryOnlineLogPo(batteryPo, mac, BatteryOnlineStatus.ONLINE, loginTime.getTime() / 1000);
        batteryOnlineLogDao.insertLog(batteryOnlineLogPo);
        batteryDao.updateBatteryStatus(batteryPo.getId(), BatteryOnlineStatus.ONLINE);
    }


    private BatteryOnlineLogPo createBatteryOnlineLogPo(BatteryPo batteryPo, String mac, BatteryOnlineStatus batteryOnlineStatus, long nowTimestamp) {
        BatteryOnlineLogPo batteryOnlineLogPo = new BatteryOnlineLogPo();
        batteryOnlineLogPo.setBatteryId(batteryPo.getId());
        batteryOnlineLogPo.setOnlineStatus(batteryOnlineStatus);
        batteryOnlineLogPo.setLoginTime(LocalDateTime.ofEpochSecond(nowTimestamp, 0, ZoneOffset.ofHours(8)));
        batteryOnlineLogPo.setMac(mac);
        batteryOnlineLogPo.setDid(batteryPo.getDid());
        return batteryOnlineLogPo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batteryLogOut(BatteryPo batteryPo, String mac,Date logoutTime) {
        BatteryOnlineLogPo batteryOnlineLog =batteryOnlineLogDao.queryBatteryOnlineRecord(batteryPo.getId());
        batteryDao.updateBatteryStatus(batteryPo.getId(), BatteryOnlineStatus.OFFLINE);
        LocalDateTime localDateTime = LocalDateTime.ofInstant(logoutTime.toInstant(), ZoneId.systemDefault());
        batteryOnlineLogDao.setBatteryLogOut(batteryOnlineLog.getId(),localDateTime);
    }

    @Override
    public List<String> queryVinList(String vin) {
        List<BatteryPo> batteryPos = batteryDao.selectByLastVin4(vin);
        return batteryPos.stream().map(BatteryPo::getVin).collect(Collectors.toList());
    }

    /**
     * 查询客户所有的电池信息
     *
     * @param customerId
     * @return
     */
    @Override
    @Deprecated
    public List<BatteryMonitorVo> selectByCustomerId(Integer customerId) {
        List<BatteryPo> batteryPos = batteryDao.selectByCustomerId(customerId);
        return selectBatteryMonitors(batteryPos);
    }

    @Override
    public List<BatteryPo> selectUserBatteries(Integer userId) {
        List<CustomerVo> customerVos = customerDao.selectCustomersByUserId(userId);
        List<BatteryPo> list = new ArrayList<>();
        customerVos.forEach(customerVo -> {
            list.addAll(batteryDao.selectByCustomerId(customerVo.getId()));
        });
        return list;
    }

    @Override
    public List<BatteryMonitorVo> selectBatteriesWithCustomer(Integer userId) {
        List<CustomerVo> customerVos = customerDao.selectCustomersByUserId(userId);
        List<Integer> customerIds = customerVos.stream().map(customerVo -> customerVo.getId()).collect(Collectors.toList());

        /**过滤掉customerId 为null或者customer已经被删除了的电池**/
        List<BatteryMapVo> batteryPos = batteryDao.selectAllBatteryByCustomerId(customerIds).stream()
                .filter(batteryPo -> batteryPo.getCustomerId() != null)
                .collect(Collectors.toList());

        List<BatteryMonitorVo> batteryMonitorVos = selectBatteryMonitors(batteryPos);

        for (BatteryMonitorVo batteryMonitorVo : batteryMonitorVos) {
            customerVos.forEach(customerVo -> {
                if (customerVo.getId().equals(batteryMonitorVo.getCustomerId())) {
                    batteryMonitorVo.setCustomerName(customerVo.getCustomerName());
                }
            });

            //补齐项目组信息
            batteryPos.forEach(batteryPo -> {
                if (batteryPo.getId().equals(batteryMonitorVo.getId())) {
                    batteryMonitorVo.setProjectId(batteryPo.getProjectTeamId());
                    batteryMonitorVo.setProjectName(batteryPo.getProjectTeamName());
                    return;
                }
            });
        }

        return batteryMonitorVos;
    }


    @Override
    public void checkBatteryOk(Integer batteryId) throws BusinessException {
        if (1 != batteryDao.updateState(batteryId, Objects.toString(BatteryStateEnum.CHECK_OK.getCode()))) {
            throw new BusinessException(ErrorCodes.ERR_UPDATE_0_ROWS);
        }
    }


    @Override
    public BatteryDailyRuntimeVo selectBatteryDailyRuntimeVo(Integer batteryId, Integer startDate, Integer endDate) {
        BatteryPo batteryPo = batteryDao.selectById(batteryId);
        MongoCursor<BmsDailyStaticResult> mongoCursor = mongoDbUtils.getDatabase().withCodecRegistry(codecRegistry)
                .getCollection(Constants.dailyStaticCollectionName)
                .find(
                        and(eq("vin", batteryPo.getVin()),
                                gte("dataDate", startDate),
                                lte("dataDate", endDate)), BmsDailyStaticResult.class)
                .iterator();
        List<BmsDailyStaticResult> list = IteratorUtils.toList(mongoCursor);
        List<BatteryDailyRuntimeVo.OnlineInfo> onlineInfoList = new ArrayList<>();
        AtomicInteger onlineDays = new AtomicInteger(0);
        list.forEach(bmsDailyStaticResult -> {
            if (bmsDailyStaticResult.isOnline()) {
                onlineDays.incrementAndGet();
            }
            BatteryDailyRuntimeVo.OnlineInfo onlineInfo = new BatteryDailyRuntimeVo.OnlineInfo(bmsDailyStaticResult.getDataDate(), bmsDailyStaticResult.getOnlineTime(), bmsDailyStaticResult.getChargeTime(), bmsDailyStaticResult.getDischargeTime());
            onlineInfoList.add(onlineInfo);
        });


        //计算时长基线
        long baseOnlineTime = onlineDays.get() == 0 ? 0 : onlineInfoList.stream().mapToLong(BatteryDailyRuntimeVo.OnlineInfo::getOnlineTime).sum() / onlineDays.get();

        BatteryDailyRuntimeVo.BatteryDailyRuntimeVoBuilder builder = BatteryDailyRuntimeVo.builder()
                .vin(batteryPo.getVin())
                .bz(batteryPo.getBz())
                .carNumber(batteryPo.getPlateNo())
                .baseOnlineTime(baseOnlineTime)
                .list(onlineInfoList);

        return builder.build();
    }

    @Override
    public PageInfo<BatteryBasicVo> pageQueryBattery(BatteryQueryVo batteryQueryVo) {

        PageInfo<BatteryBasicVo> pageInfo = PageHelper
                .startPage(batteryQueryVo.getPageNum(), batteryQueryVo.getPageSize())
                .doSelectPageInfo(() -> batteryDao.selectByBatterQueryVo(batteryQueryVo));
        return pageInfo;
    }

    /**
     * 统计电池数量
     *
     * @return
     */
    @Override
    public Integer batteryCount() {
        return batteryDao.countAll();
    }

    @Override
    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
    public void editBatteryInfo(Integer batteryId,
                                Integer projectTeamId,
                                String bz,
                                String plateNo, String deviceManager) {

        batteryDao.updateBatteryInfo(batteryId, bz, plateNo, deviceManager);

        if (projectTeamId != null) {

            batteryDao.updateBatteryProjectTeam(batteryId, projectTeamId);
        }
    }

    @Override
    public boolean checkVinIsExists(String vin) {
        if (batteryDao.countByVin(vin) > 0) {
            return true;
        } else {
            return false;
        }
    }


    @Override
    public void updateBatteryAllowCharge(String vin, String allowCharge) {
        batteryDao.updateBatteryAllowCharge(vin, allowCharge);
    }

    @Override
    public BatteryBasicVo selectBatteryById(Integer batteryId) {
        BatteryBasicVo batteryBasicVo = dozermapper.map(batteryDao.selectById(batteryId), BatteryBasicVo.class);
        return batteryBasicVo;
    }


    /**
     * 分页查询电池划拨列表信息
     *
     * @param batteryAllocationQueryVo
     * @return
     */
    @Override
    public PageInfo<BatteryAllocationVo> pageQueryBatteryAllocateInfo(BatteryAllocationQueryVo batteryAllocationQueryVo) {
        PageInfo<BatteryAllocationVo> pageInfo = PageHelper.startPage(batteryAllocationQueryVo.getPageNum(), batteryAllocationQueryVo.getPageSize())
                .doSelectPageInfo(() -> batteryDao.selectByBatteryAllocateInfo(batteryAllocationQueryVo));
        return pageInfo;
    }

    /**
     * 电池划拨
     *
     * @param customerId
     * @param batteryId
     */
    @Override
    public void batteryAllocate(Integer customerId,
                                Integer batteryId) {
        batteryDao.updateBatteryCustomer(batteryId, customerId);
    }

    /**
     * 查询电池的历史轨迹
     *
     * @param vin
     * @param fromDate
     * @param endDate
     * @return
     */
    @Override
    public List<HistoryTrackVo> getHistoryTrack(String vin, Date fromDate, Date endDate, HttpServletResponse response) {
        return Collections.emptyList();
    }


    public BatteryMonitorVo parseBatteryMonitor(BatteryPo batteryPo) {
        Protocol485Dto protocol485Dto = deviceMonitorRedisRepository.getDeviceMonitor(batteryPo.getVin());
        AssemblerFactory assemblerFactory = new AssemblerFactory();
        return assemblerFactory.convert((source, target) -> {
            Optional<Point> optionalPoint = deviceGPSRedisRepository.getDeviceGps(batteryPo.getVin());
            if (optionalPoint.isPresent()) {
                target.setLat(optionalPoint.get().getX());
                target.setLng(optionalPoint.get().getY());

            }
            target.setSoc(Convert.toStr(source.getSoc(),"0"));
            target.setBz(batteryPo.getBz());
            target.setStatus(source.getDcxtzt());
            target.setPlateNo(batteryPo.getPlateNo());
            target.setVin(batteryPo.getVin());
            target.setCustomerId(batteryPo.getCustomerId());
            target.setAllowCharge(batteryPo.getAllowCharge());
            target.setId(batteryPo.getId());
            target.setBatteryStateEnum(batteryPo.getState());
            target.setHardwareVersion(source.getDcyjbbh());
            target.setTotalAStr(Convert.toStr(source.getDczdl(),"0"));
            target.setTotalVStr(Convert.toStr(source.getDczdy(),"0"));
            target.setTickTime(source.getTickTime());
            target.setFault_num(source.getDcgzzs());
            target.setMaxSingleV(Convert.toStr(source.getDczddtdy(),"0"));
            target.setMinSingleV(Convert.toStr(source.getDczxdtdy(),"0"));
            target.setMaxSingleV_no(source.getDczddtdybh());
            target.setMinSingleV_no(source.getDczxdtdybh());
            target.setMaxT_no(source.getDczdwdbh());
            target.setMaxTStr(Convert.toStr(source.getDczdwd(),"0"));
            target.setMinTstr(Convert.toStr(source.getDczxwd(),"0"));
            target.setMinT_no(source.getDczxwdbh());

        }, protocol485Dto, BatteryMonitorVo.class);
    }


    private List<BatteryMonitorVo> selectBatteryMonitors(List<? extends BatteryPo> batteryPos) {
        List<BatteryMonitorVo> list = new ArrayList<>();
        for (BatteryPo batteryPo : batteryPos) {
            BatteryMonitorVo batteryMonitorVo = parseBatteryMonitor(batteryPo);
            list.add(batteryMonitorVo);

        }
        return list;
    }


    public static void main(String[] args) {
        StaticLog.info("{}", TimeUtil.formatTime(new Date()));

    }
}

